package com.thiswhyme.fsplus.sentinel.feign.defaultdegrade;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.cloud.sentinel.datasource.config.DataSourcePropertiesConfiguration;
import com.alibaba.cloud.sentinel.datasource.config.NacosDataSourceProperties;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @description: 自动兜底降级配置处理器
 * @author: thiswhyme
 * @date: 2021/4/25
 */
@Slf4j
public class AutoBaseDegradeConfigHandler implements CommandLineRunner {

    private NacosDiscoveryProperties nacosDiscoveryProperties;

    private SentinelProperties sentinelProperties;

    private BaseDegradeProperties baseDegradeProperties;

    public AutoBaseDegradeConfigHandler(NacosDiscoveryProperties nacosDiscoveryProperties, SentinelProperties sentinelProperties,
                                        BaseDegradeProperties baseDegradeProperties) {
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.sentinelProperties = sentinelProperties;
        this.baseDegradeProperties = baseDegradeProperties;
    }

    @Override
    public void run(String... args) {
        log.info("=====开始兜底熔断配置并注册服务监听");
        try {
            String nacosDbName = baseDegradeProperties.getNacosDbName();
            List<DegradeServiceProperties> serviceConfigs = baseDegradeProperties.getServices();
            if (StringUtils.isBlank(nacosDbName) || CollectionUtils.isEmpty(serviceConfigs)) {
                log.info("=====未读取到“熔断规则读取db”/“需要兜底熔断的出口服务”配置，跳过兜底熔断配置");
                return;
            }

            NacosDataSourceProperties dsNacos = getDegradeNacosDataSourceProperties(nacosDbName);
            if(null == dsNacos) {
                log.info("=====未获取到sentinel的datasource配置，跳过兜底熔断配置");
                return;
            }

            Properties prop= new Properties();
            prop.setProperty("serverAddr", dsNacos.getServerAddr());
            prop.setProperty("namespace", dsNacos.getNamespace());
            ConfigService configService = NacosFactory.createConfigService(prop);
            if(null == configService) {
                log.info("=====未获取到db中降级配置，跳过兜底熔断配置");
                return;
            }

            subscribe(serviceConfigs, configService, dsNacos);
        } catch (Exception e) {
            log.error("=====兜底熔断配置并注册服务监听失败", e);
        }
        log.info("=====完成兜底熔断配置并注册服务监听");
    }

    /**
     * 注册nacos服务变更事件监听，用于更新兜底的奖及配置
     * @param properties
     * @param configService
     * @param dsNacos
     * @throws NacosException
     */
    private void subscribe(List<DegradeServiceProperties> properties, ConfigService configService, NacosDataSourceProperties dsNacos) throws NacosException {
        NamingService naming = NamingFactory.createNamingService(nacosDiscoveryProperties.getServerAddr());
        if(!CollectionUtils.isEmpty(properties)) {
            for (DegradeServiceProperties property : properties) {
                naming.subscribe(property.getServiceName(), property.getGroup(), event -> {
                    if (event instanceof NamingEvent) {
                        NamingEvent namingEvent = (NamingEvent) event;
                        log.info("=====监听到服务【{}】的实例发生变更，开始更新兜底降级配置", namingEvent.getServiceName());
                        try {
                            updateConfig(namingEvent.getServiceName(), configService, dsNacos, namingEvent.getInstances(), property.getRule());
                        } catch (Exception e) {
                            log.error("=====服务【{}】兜底降级配置更新失败，原因：{}", namingEvent.getServiceName(), e.getMessage(), e);
                        }
                        log.info("=====服务【{}】兜底降级配置更新完成", namingEvent.getServiceName());
                    }
                });
            }
        }
    }

    /**
     * 更新降级配置
     * @param serviceName 资源出口对应服务名
     * @param configService 降级配置对应的nacos配置服务
     * @param dsNacos 降级配置对应的nacos数据源配置
     * @param instances 资源出口对应服务当前的所有实例
     * @throws NacosException
     */
    private void updateConfig(String serviceName, ConfigService configService, NacosDataSourceProperties dsNacos,
                              List<Instance> instances, SimpleDegradeRule ruleConfig) throws NacosException {
        //获取当前服务的降级配置
        List<IpDegradeRule> rules = Optional.ofNullable(getDegradeConfigRules(configService, dsNacos.getDataId(), dsNacos.getGroupId()))
                .orElse(new ArrayList<>(instances.size()));

        //清除该serviceName下所有针对ip:port的降级配置，添加该serviceName下当前有效实例的降级配置
        if(!CollectionUtils.isEmpty(rules)) {
            rules.removeIf(rule -> serviceName.equals(rule.getIpFrom()));
        }
        if(!CollectionUtils.isEmpty(instances)) {
            List<String> urls = instances.stream().map(instance -> instance.getIp() + ":" + instance.getPort()).collect(Collectors.toList());
            for (String url : urls) {
                IpDegradeRule newRule = new IpDegradeRule();
                BeanUtils.copyProperties(ruleConfig, newRule);
                newRule.setResource(url);
                newRule.setIpFrom(serviceName);
                rules.add(newRule);
            }
        }

        //更新最新降级配置到nacos
        String configContent = JSONObject.toJSONString(rules, SerializerFeature.PrettyFormat);
        boolean isPublishOk = configService.publishConfig(dsNacos.getDataId(), dsNacos.getGroupId(), configContent);
        if(isPublishOk) {
            log.info("降级配置更新成功，内容：\n{}", configContent);
        } else {
            log.warn("降级配置更新失败，内容：\n{}", configContent);
        }
    }

    /**
     * 获取当前服务的降级配置
     * @param configService 降级配置对应的nacos配置服务
     * @param dataId 降级配置的dataID
     * @param group 降级配置的分组
     * @return
     * @throws NacosException
     */
    private List<IpDegradeRule> getDegradeConfigRules(ConfigService configService, String dataId, String group) throws NacosException {
        String content = configService.getConfig(dataId, group, 2000);
        return JSONObject.parseArray(content, IpDegradeRule.class);
    }

    /**
     * 降级配置对应的nacos数据源配置
     * @param datasourceKey 降级配置对应的nacos数据源配置key
     * @return
     */
    private NacosDataSourceProperties getDegradeNacosDataSourceProperties(String datasourceKey) {
        if(StringUtils.isBlank(datasourceKey) || null == sentinelProperties) {
            return null;
        }
        Map<String, DataSourcePropertiesConfiguration> dsMap = sentinelProperties.getDatasource();
        if(CollectionUtils.isEmpty(dsMap)) {
            return null;
        }
        DataSourcePropertiesConfiguration config = sentinelProperties.getDatasource().get(datasourceKey);
        if(null == config) {
            return null;
        }
        return config.getNacos();
    }
}
